//---------------[CSE420]----------[Project #4]----------[Part #3]---------------
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libspe2.h>
#include <cbe_mfc.h>
#include <pthread.h>
#include <unistd.h>
#include <math.h>
#include "common.h"


//------------------------------[Data Structures]--------------------------------
/* Data structure for running SPE thread */
typedef struct ppu_pthread_data
{
	spe_context_ptr_t   speid;
	pthread_t           pthread;
	uint64_t           *argp;
} ppu_pthread_data_t;


//------------------------------[Global Variables]-------------------------------
int* matrixA_InPPE              QWORD_ALIGNED;
int* matrixB_InPPE              QWORD_ALIGNED;
int* matrixC_InPPE              QWORD_ALIGNED;
int result                      QWORD_ALIGNED;

extern spe_program_handle_t spu;
CONTROL_BLOCK cb QWORD_ALIGNED;
ppu_pthread_data_t data;


//----------------------------[Forward Declarations]-----------------------------
void *ppu_pthread_function(void *arg);
void initMatrices();
void printMatrix(char *name, int *matrix);


//------------------------------------[Main]-------------------------------------
int main(int argc, char *argv[])
{
	int rc;
	initMatrices();
	//printMatrix("A", &matrixA_InPPE);
	//printMatrix("B", &matrixB_InPPE);

/********************************************************/
#ifdef TIMING
long unsigned int start_t, end_t;
float total_t = 0;
#endif
/********************************************************/

   // Multiply sub matrices
   for (int i_block = 0; i_block < N; i_block+=BLOCK_SIZE)
   {
      for (int j_block = 0; j_block < N; j_block+=BLOCK_SIZE)
      {
         for (int k_block = 0; k_block < N; k_block+=BLOCK_SIZE)
         {
            //int* a = (int*)malloc(BLOCK_SIZE_SQUARED * sizeof(int)) QWORD_ALIGNED;
            //int* b = (int*)malloc(BLOCK_SIZE_SQUARED * sizeof(int)) QWORD_ALIGNED;
            //int* c = (int*)malloc(BLOCK_SIZE_SQUARED * sizeof(int)) QWORD_ALIGNED;
            int a[BLOCK_SIZE_SQUARED] QWORD_ALIGNED;
            int b[BLOCK_SIZE_SQUARED] QWORD_ALIGNED;
            int c[BLOCK_SIZE_SQUARED] QWORD_ALIGNED;

            for (int i = 0; i < BLOCK_SIZE; ++i)
            {
               for (int j = 0; j < BLOCK_SIZE; ++j)
               {
                  for (int k = 0; k < BLOCK_SIZE; ++k)
                  {
                     a[i * BLOCK_SIZE + k] = matrixA_InPPE[(i_block + i) * N + (k_block + k)];
                     b[k * BLOCK_SIZE + j] = matrixB_InPPE[(k_block + k) * N + (j_block + j)];
                     c[i * BLOCK_SIZE + j] = matrixC_InPPE[(i_block + i) * N + (j_block + j)];
                  }
               }
            }

            //****************************************************
            
			   cb.blockA = &a;
			   cb.blockB = &b;
			   cb.SPU_result = &c;
            
			   /* Create SPE context and load SPE program into the SPE context */
			   if ((data.speid = spe_context_create (0, NULL)) == NULL)
			   {
				   fprintf(stderr, "Failed spe_context_create(errno=%d strerror=%s)\n", errno, strerror(errno));
				   exit (3);
			   }
  
			   /* Load program. */
			   if ((rc = spe_program_load (data.speid, &spu)) != 0)
			   {
				   fprintf (stderr, "Failed spe_program_load(errno=%d strerror=%s)\n", errno, strerror(errno));
				   exit (3);
			   }

			   /* Pass Control Block address to SPU. */
			   data.argp = (uint64_t *) &cb;

/********************************************************/
#ifdef TIMING
start_t = __mftb();
#endif
/********************************************************/

			   /* Create SPE pthreads */
			   if ((rc = pthread_create (&data.pthread, NULL, &ppu_pthread_function, &data)) != 0)
			   {
				   fprintf (stderr, "Failed pthread_create(errno=%d strerror=%s)\n", errno, strerror(errno));
				   exit (3);
			   }
  
			   /* Wait for SPE to complete */
			   if ((rc = pthread_join (data.pthread, NULL)) != 0)
			   {
				   fprintf (stderr, "Failed pthread_join(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
				   exit (1);
			   }
            
            // Store block that we computed back into the whole matrix
            for (int i = 0; i < BLOCK_SIZE; ++i)
               for (int j = 0; j < BLOCK_SIZE; ++j)
                  for (int k = 0; k < BLOCK_SIZE; ++k)
                     matrixC_InPPE[(i_block + i) * N + (j_block + j)] = cb.SPU_result[i * BLOCK_SIZE + j];

/********************************************************/
#ifdef TIMING
end_t = __mftb();
total_t += (float)((end_t-start_t)/79.8);
#endif
/********************************************************/

			   /* Destroy the SPE contexts */
			   if ((rc = spe_context_destroy (data.speid)) != 0)
			   {
				   fprintf (stderr, "Failed spe_context_destroy(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
				   exit (1);
			   }
            
            //****************************************************

            // Free memory
            //free(a);
            //free(b);
            //free(c);
         }
      }
   }

/********************************************************/
#ifdef TIMING
printf("\n\n\nTotal time used by SPE: %f us\n\n\n", total_t);
#endif
/********************************************************/

	printMatrix("C", matrixC_InPPE);

   free(matrixA_InPPE);
   free(matrixB_InPPE);
   free(matrixC_InPPE);

	return 0;
}

//---------------------------------[Functions]-----------------------------------
/* Create and run one SPE thread */
void *ppu_pthread_function(void *arg)
{
	ppu_pthread_data_t *datap = (ppu_pthread_data_t *)arg;
	int rc;
	unsigned int entry = SPE_DEFAULT_ENTRY;
	if ((rc = spe_context_run(datap->speid, &entry, 0, datap->argp, NULL, NULL)) < 0)
	{
		fprintf(stderr, "Failed spe_context_run(rc=%d, errno=%d, strerror=%s)\n", rc, errno, strerror(errno));
		exit(1);
	}
	pthread_exit(NULL);
}

/* Initializes A and B with random integers from (0, 100] */
void initMatrices()
{
   matrixA_InPPE = (int*)malloc(MATRIX_SIZE * sizeof(int));
   matrixB_InPPE = (int*)malloc(MATRIX_SIZE * sizeof(int));
   matrixC_InPPE = (int*)malloc(MATRIX_SIZE * sizeof(int));

	for(int row = 0; row < N; row++)
	{
		for(int col = 0; col < N; col++)
		{
			matrixA_InPPE[row * N + col] = random() % 100;
			matrixB_InPPE[row * N + col] = random() % 100;
         matrixC_InPPE[row * N + col] = 0;
		}
	}
}

/* Prints a matrix */
void printMatrix(char *name, int *matrix)
{
	printf("Matrix %s:\n", name);
	for(int row = 0; row < N; row++)
	{
		for(int col = 0; col < N; col++)
		{
			printf("%8d", matrix[row * N + col]);
		}
		printf("\n");
	}
}
//------------------------------------[END]--------------------------------------
